#include <SDL/SDL.h>
#include <GL/glew.h>
//#include <glm/glm.hpp>
//#include <glm/gtc/matrix_transform.hpp>

#include <cstdlib>
#include <iostream>
#include <string.h>
#include <unistd.h>

#include "sdlglutils.h"
#include "trackballcamera.h"
#include "loadFile.h"
#include "drawObject.h"
#include "cubeMap.h"
#include "shadow.h"

#define OBJ_FRAG_SHAD "object.frag"
#define OBJ_VERT_SHAD "object.vert"
#define SHADOW_FRAG_SHAD "shadow.frag"
#define SHADOW_VERT_SHAD "shadow.vert"


double angleX=0.0;
double angleZ=0.0;

float lPosX=0.0;
float lPosY=0.0;
float lPosZ=5.9;

TrackBallCamera * camera;
CubeMap * cubeMap;
DrawObject * drawObject;
Shadow * shadow;

GLuint ObjVertShader, ObjFragShader, ObjProgram;
GLuint ShadowVertShader, ShadowFragShader, ShadowProgram;

GLuint fbo;
GLuint fboShadow;

void loadShader();

void checkStatus(GLenum target);

void chercheExtension(const char * nom_extension_recherchee);

void afficheExtensions();

void getCubeMap(int w, int h);

void getShadow(float lPosX,float lPosY, float lPosZ,int w,int h);

void setShadowMatrix(int w,int h);

void drawScene(bool withObj);


void stop()
{
    delete camera;
    delete cubeMap;
    delete drawObject;
    delete shadow;

    glDeleteFramebuffers(1,&fbo);
    glDeleteFramebuffers(1,&fboShadow);

    glDeleteShader(ObjVertShader);
    glDeleteShader(ObjFragShader);
    glDeleteProgram(ObjProgram);

    glDeleteShader(ShadowVertShader);
    glDeleteShader(ShadowFragShader);
    glDeleteProgram(ShadowProgram);

    SDL_Quit();
}

/*
int LookAt[6][9]=
    {
        {2,0,2,0,0,2,0,-1,0},  //+X
        {0,2,2,0,0,2,0,0,-1},  //+Y
        {0,0,2,0,0,0,1,0,0},  //+Z
        {-2,0,2,0,0,2,0,-1,0}, //-X
        {0,-2,2,0,0,2,0,0,1}, //-Y
        {0,0,2,0,0,4,1,0,0}   //-Z
    };
*/


int main(int argc, char *argv[])
{

    int w=640;
    int h=480;
    SDL_Event event;
    SDL_Init(SDL_INIT_VIDEO);
    atexit(stop);
    SDL_WM_SetCaption("OpenGL GLSL Application", NULL);
    SDL_SetVideoMode(w, h, 32, SDL_OPENGL);

    GLenum glew=glewInit();
    if (glew!=GLEW_OK)
    {
        fprintf(stderr,"Impossible d'initialiser glew\n");
        return 0;
    }
    cubeMap = new CubeMap(false);
    cubeMap->loadCubeMapTexture();

    drawObject=new DrawObject();

    shadow=new Shadow(w,h);

    glGenFramebuffers(1,&fbo);
    glGenFramebuffers(1,&fboShadow);

    camera = new TrackBallCamera();
    camera->setScrollSensivity(0.1);
    /*
    chercheExtension("GL_ARB_framebuffer_object");
    chercheExtension("GL_EXT_framebuffer_object");
    */

    loadShader();

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_CULL_FACE);


    Uint32 last_time = SDL_GetTicks();
    Uint32 current_time,ellapsed_time;

    for(;;)
    {
        while (SDL_PollEvent(&event))
        {
            switch(event.type)
            {
            case SDL_QUIT:
                exit(0);
                break;

            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                case SDLK_p:
                    takeScreenshot("test.bmp");
                    break;
                case SDLK_ESCAPE:
                    exit(0);
                    break;
                default :
                    camera->OnKeyboard(event.key);
                }
                break;
            case SDL_MOUSEMOTION:
                camera->OnMouseMotion(event.motion);
                break;
            case SDL_MOUSEBUTTONUP:
            case SDL_MOUSEBUTTONDOWN:
                camera->OnMouseButton(event.button);
                break;
            }
        }

        current_time=SDL_GetTicks();
        ellapsed_time=current_time-last_time;
        last_time=current_time;

        angleX+=0.05*ellapsed_time;
        angleZ+=0.05*ellapsed_time;

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        gluPerspective(70,(double)w/h,1,1000);
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        camera->look();
        //    int i=5;
        //gluLookAt(LookAt[i][0],LookAt[i][1],LookAt[i][2],LookAt[i][3],LookAt[i][4],LookAt[i][5],LookAt[i][6],LookAt[i][7],LookAt[i][8]);
        //drawAxis(5);
        setShadowMatrix(w,h);
        glUseProgram(ShadowProgram);
        glActiveTexture(GL_TEXTURE7);
        glBindTexture(GL_TEXTURE_2D,shadow->getID());
        glActiveTexture(GL_TEXTURE0);

        drawScene(true);

        glActiveTexture(GL_TEXTURE7);
        glBindTexture(GL_TEXTURE_2D,0);
        glActiveTexture(GL_TEXTURE0);

        glUseProgram(0);

        drawObject->DrawGround(10,16);

        if (!cubeMap->isStatic())
            getCubeMap(w,h);
        //cubeMap->displayCubeMapTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_X);
        getShadow(lPosX,lPosY,lPosZ,w,h);
        // shadow->displayShadowMapTexture(fboShadow);

        glFlush();
        SDL_GL_SwapBuffers();

    }

    return 0;
}



void getShadow(float lPosX,float lPosY, float lPosZ,int w,int h)
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboShadow);
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);

    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadow->getID(), 0);
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glPushMatrix();
    glLoadIdentity();
    gluLookAt(lPosX,lPosY,lPosZ,0,0,0,1,0,0);

    drawScene(true);

    glUseProgram(0);
    glPopMatrix();

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

}

void setShadowMatrix(int w,int h)
{

    double modelView[16];
    double projection[16];

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    gluLookAt(lPosX,lPosY,lPosZ,0,0,0,1,0,0);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
    glPopMatrix();

    glGetDoublev(GL_PROJECTION_MATRIX, projection);


    GLdouble S[16] = {0.5, 0.0, 0.0, 0.0,
                      0.0, 0.5, 0.0, 0.0,
                      0.0, 0.0, 0.5, 0.0,
                      0.5, 0.5, 0.5, 1.0
                     };

    glMatrixMode(GL_TEXTURE);
    glActiveTexture(GL_TEXTURE7);
    glLoadIdentity();
    glLoadMatrixd(S);
    glMultMatrixd(projection);
    glMultMatrixd(modelView);

    glMatrixMode(GL_MODELVIEW);

    glActiveTexture(GL_TEXTURE0);
}


void getCubeMap(int w, int h)
{
    GLenum CubeMapTarget[6] =
    {
        GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
        GL_TEXTURE_CUBE_MAP_POSITIVE_X,
        GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
    };

    int LookAt[6][9]=
    {
        {0,0,1,1,0,1,0,0,-1},  //+X
        {0,0,1,0,1,1,0,0,-1},  //+Y
        {0,0,1,0,0,2,1,0,0},  //+Z
        {0,0,1,-1,0,1,0,0,-1}, //-X
        {0,0,1,0,-1,1,0,0,-1}, //-Y
        {0,0,1,0,0,0,1,0,0}   //-Z
    };


    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glViewport(0, 0, 256, 256);

    for(int i=0; i<6; i++)
    {

        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, CubeMapTarget[i], cubeMap->getID(), 0);
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glPushMatrix();
        glLoadIdentity();
        gluLookAt(LookAt[i][0],LookAt[i][1],LookAt[i][2],LookAt[i][3],LookAt[i][4],LookAt[i][5],LookAt[i][6],LookAt[i][7],LookAt[i][8]);
        drawScene(false);
        glPopMatrix();
    }

    glViewport(0, 0, w, h);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

}


void loadShader()
{

    ObjVertShader=LoadShaderFile(GL_VERTEX_SHADER,OBJ_VERT_SHAD);
    ObjFragShader=LoadShaderFile(GL_FRAGMENT_SHADER,OBJ_FRAG_SHAD);
    ObjProgram=glCreateProgram();
    glAttachShader(ObjProgram,  ObjVertShader);
    glAttachShader(ObjProgram, ObjFragShader);
    glLinkProgram(ObjProgram);

    ShadowVertShader=LoadShaderFile(GL_VERTEX_SHADER,SHADOW_VERT_SHAD);
    ShadowFragShader=LoadShaderFile(GL_FRAGMENT_SHADER,SHADOW_FRAG_SHAD);
    ShadowProgram=glCreateProgram();
    glAttachShader(ShadowProgram,  ShadowVertShader);
    glAttachShader(ShadowProgram, ShadowFragShader);
    glLinkProgram(ShadowProgram);

    glUseProgram(ObjProgram);
    GLuint id = glGetUniformLocation(ObjProgram, "tex");
    glUniform1i(id, 0);

    id = glGetUniformLocation(ObjProgram, "cube");
    glUniform1i(id, 1);

    id = glGetUniformLocation(ObjProgram, "lPos");
    glUniform4f(id, lPosX, lPosY, lPosZ, 0.0);

    id =  glGetUniformLocation(ObjProgram, "lAmbient");
    glUniform4f(id, 0.3, 0.3, 0.3, 1.0);  // valeur par défaut GL_ambient

    id =  glGetUniformLocation(ObjProgram,"lDiffuse");
    glUniform4f(id, 0.7, 0.7, 0.7, 1.0);  // valeur par défaut GL_diffuse

    id =  glGetUniformLocation(ObjProgram, "lSpecular");
    glUniform4f(id, 1.0, 1.0, 1.0, 1.0);  // valeur par défaut GL_specular

    glUseProgram(ShadowProgram);
    id = glGetUniformLocation(ShadowProgram, "lPos");
    glUniform4f(id, lPosX, lPosY, lPosZ, 0.0);

    id = glGetUniformLocation(ShadowProgram, "tex");
    glUniform1i(id, 0);

    id = glGetUniformLocation(ShadowProgram, "shadow");
    glUniform1i(id, 7);

    glUseProgram(0);

}

void drawScene(bool withObj)
{
    glColor3ub(255,255,255);
    glBindTexture(GL_TEXTURE,0);
    drawObject->DrawHouse(10,16,6,3);

    //drawObject->DrawLight(lPosZ);

    if (withObj)
    {
        glPushMatrix();
        glTranslatef(0,0,-0.99);

        glUseProgram(ObjProgram);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_CUBE_MAP,cubeMap->getID());
        glActiveTexture(GL_TEXTURE0);

        glColor3ub(255,255,255);
        drawObject->DrawGlobe(angleZ);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_CUBE_MAP,0);
        glActiveTexture(GL_TEXTURE0);
        glUseProgram(0);
        glPopMatrix();
    }
    glPushMatrix();
    glTranslatef(1,3,-0.5);
    glRotated(30,0,0,1);
    drawObject->DrawCube();
    glPopMatrix();

    glPushMatrix();
    glTranslatef(2,-3,0.3);
    drawObject->DrawRocket();
    glPopMatrix();

    glPushMatrix();
    glTranslatef(-4,-4,0);
    drawObject->DrawTable(4,2);
    glPopMatrix();
    glPushMatrix();
    glTranslatef(-3.5,1,0);
    glRotated(-90,0,0,1);
    drawObject->DrawChair();
    glPopMatrix();
    glPushMatrix();
    glTranslatef(-1,-2,0);
    glRotated(180,0,0,1);
    drawObject->DrawChair();
    glPopMatrix();

}


void checkStatus(GLenum target)
{
    GLenum status=glCheckFramebufferStatus(target);

    switch(status)
    {

    case GL_FRAMEBUFFER_COMPLETE:
        std::cout<<"complete"<<std::endl;
        break;
    case GL_FRAMEBUFFER_UNDEFINED:
        std::cout<<"undefined"<<std::endl;
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
        std::cout<<"incomplete attachement"<<std::endl;
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
        std::cout<<"incomplete missing attachement"<<std::endl;
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
        std::cout<<"incomplete draw buffer"<<std::endl;
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
        std::cout<<"incomplete read buffer"<<std::endl;
        break;
    case GL_FRAMEBUFFER_UNSUPPORTED:
        std::cout<<"unsuported"<<std::endl;
        break;

    case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
        std::cout<<"incomplete multisample"<<std::endl;
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
        std::cout<<"incomplete layer targets"<<std::endl;
        break;

    }

}

void chercheExtension(const char * nom_extension_recherchee)
{
    /* recuperation de la liste des extensions supportees */
    const unsigned char *exts = glGetString(GL_EXTENSIONS);

    /* on regarde si exts contient ce qu'on demande... */
    if(strstr((const char*)exts, nom_extension_recherchee) == NULL)
    {
        /* extension non supportee... :-( */
        fprintf(stderr, "extension '%s' non supportee\n", nom_extension_recherchee);
    }
    else
    {
        /* OK, extension supportee */
        fprintf(stdout, "extension '%s' supportee\n", nom_extension_recherchee);
    }
}

void afficheExtensions()
{
    /* recuperation de la liste des extensions supportees */
    const unsigned char *exts = glGetString(GL_EXTENSIONS);

    std::cout<<exts<<std::endl;
}



